#ifndef __GAME_H__
#define __GAME_H__
#include <irrlicht.h>
#include <iostream>
#include <vector>

/*
  game.h - game object encapsulates the procedures of the main function
  Ben Gilbert
*/

const unsigned numberOfBoxes = 1000;
irr::u32 changeTime = 0;

class Box
{
 public:
  irr::scene::ISceneNode* sceneNode;
  irr::scene::ISceneManager* smgr;
  irr::core::vector3df direction;
	
 Box(irr::scene::ISceneManager* s) : 
  smgr(s), direction(irr::core::vector3df(1,1,1)) {}
  void createBox(irr::video::IVideoDriver* driver) 
  {
    float boxRadius = 5.0;
    sceneNode = smgr->addCubeSceneNode(boxRadius*2.0);
    if (!sceneNode) {
      std::cout << "Error: Could not create Cube Scene Node.\n" << std::endl;
      exit(1); 
    }
    std::string sceneNodeTexture("../../media/t351sml.jpg");
    sceneNode->setMaterialTexture(0, driver->getTexture(sceneNodeTexture.c_str()));
    sceneNode->setMaterialFlag(irr::video::EMF_LIGHTING, false);
    const irr::core::vector3df default_sceneNode_position(irr::core::vector3df(0,0,0));
    sceneNode->setPosition(default_sceneNode_position);
  }
};

irr::core::vector3df randomVector() {
  irr::core::vector3df vec;
  vec.X = static_cast<irr::f32> (rand() % 200 - 100);
  vec.Y = static_cast<irr::f32> (rand() % 200 -100);
  vec.Z = static_cast<irr::f32> (rand() % 200 - 100);
  vec.normalize();
  return vec;
}

bool endGame = false;
irr::core::vector3df velocity(1, 1, 1);

class GameEventReceiver : public irr::IEventReceiver
{
 public:
  virtual bool OnEvent(const irr::SEvent& event)
  {
    if (event.EventType == irr::EET_KEY_INPUT_EVENT )
      {
	switch(event.KeyInput.Key)
	  {
	  case irr::KEY_ESCAPE:
	    {
	      endGame = true;
	      break;
	    }
	  default : {}
	  }

      }

    return false;
  }
};

class Game{
 public:

  Game() {}
  void createDevice() 
  {
    srand(0);

    const irr::core::dimension2d<irr::s32> screen_dimensions(640, 480);	
    const irr::video::E_DRIVER_TYPE device_type = irr::video::EDT_OPENGL;
    const irr::u32 bits_per_pixel = 16;
    const bool fullscreen = false;
    const bool stencil_buffer = false;
    const bool vertical_sync = false;
    gameEventReceiver = new GameEventReceiver();

    device = irr::createDevice(device_type, screen_dimensions, 
			       bits_per_pixel, fullscreen, stencil_buffer, 
			       vertical_sync, gameEventReceiver
			       );
    if (!device)
      {
	std::cout << "Error: Could not create Irrlicht device.\n" << std::endl;
	exit(1); 
      }
  }
  void createVideoDriver() 
  {
    driver = device->getVideoDriver(); 
    if(!device) 
      {
	std::cout << "Error: Could not create Video Driver.\n" << std::endl;
	exit(1);
      }
  }
  void createSceneManager()
  {
    smgr = device->getSceneManager();   
    if(!smgr) 
      {
	std::cout << "Error: Could not create Scene Manager.\n" << std::endl;
	exit(1);
      }
  }
  void loadQuakeMap()
  {
    const std::string quake_map_zip_file( "../../media/map-20kdm2.pk3");
    device->getFileSystem()->addZipFileArchive( quake_map_zip_file.c_str() );
  }
  void loadQuakeMesh()
  {
    const std::string quake_map_file( "20kdm2.bsp" );
    quake_mesh = smgr->getMesh( quake_map_file.c_str() ); 
    if (!quake_mesh)
      {
	std::cout << "Error: Could not load Quake Mesh.\n" << std::endl;
	exit(1);
      }
  }
  void createQuakeSceneNode() {
    quake_node = smgr->addOctTreeSceneNode(quake_mesh->getMesh(0));
    if(!quake_node) 
      {
	std::cout << "Error: Could not create Quake Octree Scene Node.\n" << std::endl;
	exit(1);
      }
    const irr::core::vector3df default_quake_node_position(-1350,-130,-1400);
    quake_node->setPosition(default_quake_node_position);
  }

  void createFPSCamera() 
  {
    irr::scene::ISceneNode* camera_parent = 0;
    const irr::f32 rotate_speed = 100.0f;
    const irr::f32 move_speed = 300.0f;
    const irr::s32 id = -1;
    irr::SKeyMap* key_map_array = 0;
    const irr::s32 key_map_size = 0;
    const bool no_vertical_movement = false;
    const irr::f32 jump_speed = 100.f;

    camera = smgr->addCameraSceneNodeFPS(
					 camera_parent, 
					 rotate_speed,
					 move_speed,
					 id,
					 key_map_array,
					 key_map_size,
					 no_vertical_movement,
					 jump_speed
					 );
    const irr::core::vector3df default_camera_position(-100,50,-150);
    camera->setPosition(default_camera_position);
  }  
  void createCamera() 
  {
   const irr::core::vector3df default_camera_position(-100,50,-150);
   irr::scene::ISceneNode* camera_parent = 0;
   const irr::core::vector3df lookat = irr::core::vector3df(0, 0, 100);
   const irr::s32 id = -1;
   camera = smgr->addCameraSceneNode(camera_parent, 
				     default_camera_position, 
				     lookat, 
				     id
				     ); 
  }
  void createTriangleSelector()
  {
    const irr::u32 min_polygons_octree_node = 128;
    selector = 0;
    selector = smgr->createOctTreeTriangleSelector(quake_mesh->getMesh(0), 
						   quake_node, min_polygons_octree_node);
    quake_node->setTriangleSelector(selector);
    if(!selector)
      {
	std::cout << "Error: Could not create Triangle Selector.\n" << std::endl;
	exit(1); 
      }
  }
  void addCollisionResponseAnimatorToCamera() 
  {

    const irr::core::vector3df ellipsoidRadius = irr::core::vector3df(30,50,30);
    const irr::core::vector3df gravityPerSecond = irr::core::vector3df(0,-3.0f,0);
    const irr::core::vector3df ellipsoidTranslation = irr::core::vector3df(0,50,0);
    irr::scene::ISceneNodeAnimator* anim = 
      smgr->createCollisionResponseAnimator (
					     selector, 
					     camera, 
					     ellipsoidRadius,
					     gravityPerSecond, 
					     ellipsoidTranslation
					     );
    camera->addAnimator(anim);
    anim->drop();
  }
  void createTarget() 
  {
    device->getCursorControl()->setVisible(false);
    const std::string target_bitmap( "../../media/particle.bmp");
    target = smgr->addBillboardSceneNode();
    target->setMaterialType(irr::video::EMT_TRANSPARENT_ADD_COLOR );
    target->setMaterialTexture(0, driver->getTexture( target_bitmap.c_str() ));
    target->setMaterialFlag(irr::video::EMF_LIGHTING, false);
    target->setMaterialFlag(irr::video::EMF_ZBUFFER, false);

    const irr::core::dimension2df target_size(20.0f, 20.0f);
    target->setSize(target_size);
  }
  void addLight() 
  {
    irr::scene::ISceneNode* light_parent = 0;
    const irr::core::vector3df light_position = irr::core::vector3df(0,0,0);
    const irr::video::SColorf light_color = irr::video::SColorf(1.0f, 1.0f, 1.0f);
    const irr::f32 light_radius = 100.0f; 
    const irr::s32 light_id = -1;
    smgr->addLightSceneNode(light_parent, light_position,
			    light_color,
			    light_radius, light_id);
  }

  int loop(unsigned frameLimit) 
  {
    int lastFPS = -1;
    unsigned frameCount = 0;

    while(device->run()  && !endGame ) 
      {
	if( frameLimit != 0)
	  {
	    if(frameCount >= frameLimit)
	      {
		break;
	      }
	  }
	if (device->isWindowActive())
	  {
	    driver->beginScene(true, true, 0);
	    updatePositions(!frameCount);
	    smgr->drawAll();
	    placeTarget();
	    driver->endScene();
	    lastFPS = updateCaption(lastFPS);
	    //std::cout << lastFPS << std::endl;
	  }
	frameCount++;
      }
    device->drop();

    return 0;
  }
	
  void createBoxes() 
  {
    const irr::core::vector3df default_box_position(irr::core::vector3df(0,0,0));
    std::string boxTexture("../../media/t351sml.jpg");
    for(unsigned i = 0; i < numberOfBoxes; i++) {
      boxVector.push_back(new Box(smgr));
      boxVector[i]->createBox(driver);
      if (!boxVector[i]) {
	std::cout << "Error: Could not create Cube Scene Node.\n" << std::endl;
	exit(1); 
      }
    }

  }

  void addCollisionResponseAnimatorToBoxes() 
  {
    for(unsigned i = 0; i < numberOfBoxes; i++) 
      {
	const irr::core::aabbox3d<irr::f32>& boundingBox = boxVector[i]->sceneNode->getBoundingBox();
	irr::core::vector3df ellipsoidRadius = boundingBox.MaxEdge - boundingBox.getCenter();
	const irr::core::vector3df gravityPerSecond = irr::core::vector3df(0, 0.0f,0);
	irr::scene::ISceneNodeAnimator* anim = 
	  smgr->createCollisionResponseAnimator (
						 selector, 
						 boxVector[i]->sceneNode, 
						 ellipsoidRadius,
						 gravityPerSecond
						 );
	boxVector[i]->sceneNode->addAnimator(anim);
	anim->drop();

      }
  }
 private:
  irr::IrrlichtDevice* device;
  irr::video::IVideoDriver* driver;
  irr::scene::ISceneManager* smgr;
  irr::gui::IGUIEnvironment* guienv;
  irr::scene::IAnimatedMesh* quake_mesh;
  irr::scene::ISceneNode* quake_node;
  irr::scene::IBillboardSceneNode * target;
  irr::scene::ICameraSceneNode* camera;
  irr::scene::ITriangleSelector* selector;
  GameEventReceiver* gameEventReceiver;	
  std::vector<Box*> boxVector;


  void placeTarget() 
  {
    irr::core::line3d<irr::f32> line;
    line.start = camera->getPosition();
    line.end = line.start + (camera->getTarget() - line.start).normalize() * 1000.0f;

    irr::core::vector3df intersection;
    irr::core::triangle3df tri;
    if (smgr->getSceneCollisionManager()->getCollisionPoint(line, selector, 
							    intersection, tri))
      {
	target->setPosition(intersection);
	driver->setTransform(irr::video::ETS_WORLD, irr::core::matrix4());
      }
  }
  int updateCaption(int lastFPS) 
  {
    int fps = driver->getFPS();

    if (lastFPS != fps)
      {
	irr::core::stringw caption = L"Collision Detection and Response Benchmark [";
	caption += driver->getName();
	caption += "] FPS:";
	caption += fps;

	device->setWindowCaption(caption.c_str());
      }
    return fps; 

  }
  void updatePositions(bool isInitial)
  {

    if( isInitial)
      {
	for(unsigned i = 0; i < numberOfBoxes; i++)
	  {
	    velocity = randomVector() * 1.0;
	    boxVector[i]->direction = velocity;
				
	    irr::core::vector3df v = boxVector[i]->sceneNode->getPosition();
	    v += boxVector[i]->direction * 1.0;
	    boxVector[i]->sceneNode->setPosition(v);
	  }
      } else
      {
	for(unsigned i = 0; i < numberOfBoxes; i++)
	  {
	    irr::core::vector3df v = boxVector[i]->sceneNode->getPosition();
	    v += boxVector[i]->direction * 1.0;
	    boxVector[i]->sceneNode->setPosition(v);
	  }
      }
  }

};
#endif
